#include <stdio.h>
#define __USE_GNU /* required for strnlen() */
#include <string.h>
#undef __USE_GNU
#include <assert.h>

#include "common.h"
#include "ctrl_channel.h"

#define MAX_MSGLEN 1024

extern logger_t log;

static int read_next_line(link_t* target, char* buffer, int size);
static int message_to_ctrl(const char* msg_str, ctrl_message* msg);
static int ctrl_to_message(const ctrl_message* msg, char* buffer);

#ifdef TEST_CTRL_STRINGS
static int validate_string = 1;
#else
static int validate_string = 0;
#endif

int ctrl_send_receive(link_t* target, ctrl_message* msg, ctrl_message* reply)
{
	char buffer[MAX_MSGLEN];

	if (target == NULL) {
		return CTRL_ERROR;
	}

	if (msg != NULL) {
		if (ctrl_to_message(msg, buffer) != CTRL_SUCCESS) {
			return CTRL_ERROR;
		}

		logger_add_message(&log, target->port, buffer);

		DPRINT("[cc] sending message: %s", buffer);
		if (link_send_raw(target, buffer, strlen(buffer)) != LINK_SUCCESS) {
			DPRINT("[cc] failed to send message");
			return CTRL_ERROR;
		}
	}

	if (reply != NULL) {
		if (read_next_line(target, buffer, sizeof(buffer)) != CTRL_SUCCESS) {
			DPRINT("[cc] failed to receive message");
			return CTRL_ERROR;
		}

		logger_add_message(&log, target->port, buffer);

		DPRINT("[cc] received: %s", buffer);

		if (message_to_ctrl(buffer, reply) != CTRL_SUCCESS) {
			return CTRL_PARSE_ERROR;
		}
	}

	return CTRL_SUCCESS;
}

static int read_next_line(link_t* target, char* buffer, int size)
{
	int n, received = 0;
	int lrc;
	char c;

	while ((lrc = link_receive_raw(target, &c, 1, &n)) == LINK_SUCCESS && received < size) {
		assert(n == 1);
		if (n == 0) {
			continue;
		}

		buffer[received++] = c;

		if (received >= 2 && buffer[received-2] == '\r' && buffer[received-1] == '\n') { /* check for \r\n */
			break;
		}
	}

	if (lrc == LINK_ERROR || received >= size) {
		return CTRL_ERROR;
	}

	buffer[received] = 0; /* \0 is not sent */

	return CTRL_SUCCESS;
}

static int message_to_ctrl(const char* in_msg_str, ctrl_message* msg)
{
	int reply_num;
	char msg_str[MAX_MSGLEN];
	int msg_len;

	strncpy(msg_str, in_msg_str, sizeof(msg_str));
	msg_len = strnlen(msg_str, sizeof(msg_str)); /* will return MAX_MSGLEN if there is no '\0' in the buffer */

	if (msg_len < 3 || msg_len >= sizeof(msg_str)) {
		DPRINT("[cc] msg too short/long");
		return CTRL_PARSE_ERROR;
	}

	msg_str[strlen(msg_str) - 2] = '\0'; /* remove the trailing '\r\n' */

	if (sscanf(msg_str, "%3d", &reply_num) == 1)
	{
		switch (reply_num) {
			case 227: {
				int port_h, port_l;

				if (sscanf(msg_str + 4, "Passive %d, %d", &port_h, &port_l) != 2) {
					return CTRL_PARSE_ERROR;
				}

				msg->ctrl = PASV_ACK;
				msg->port = (port_h << 8) | port_l;
				break;
			}

			case 501:
				if (validate_string && strcmp(msg_str + 4, "syntax error.") != 0) {
					return CTRL_PARSE_ERROR;
				}

				msg->ctrl = SYNTAX_ERROR;
				break;

			case 550:
				if (validate_string && strcmp(msg_str + 4, "Requested action not taken.") != 0) {
					return CTRL_PARSE_ERROR;
				}

				msg->ctrl = ERR_NO_ACTION;
				break;

			case 150:
				if (validate_string && strcmp(msg_str + 4, "About to open a data connection.") != 0) {
					return CTRL_PARSE_ERROR;
				}

				msg->ctrl = OPEN_DATA;
				break;

			case 425:
				if (validate_string && strcmp(msg_str + 4, "Can't open data connection.") != 0) {
					return CTRL_PARSE_ERROR;
				}

				msg->ctrl = ERR_OPEN_DATA;
				break;

			case 426:
				if (validate_string && strcmp(msg_str + 4, "Connection closed; transfer aborted.") != 0) {
					return CTRL_PARSE_ERROR;
				}

				msg->ctrl = ERR_DATA_CLOSED;
				break;

			case 226:
				if (validate_string && strcmp(msg_str + 4, "Requested file action successful.") != 0) {
					return CTRL_PARSE_ERROR;
				}

				msg->ctrl = DATA_FIN;
				break;

			case 200:
				if (validate_string && strcmp(msg_str + 4, "Command okay.") != 0) {
					return CTRL_PARSE_ERROR;
				}

				msg->ctrl = PORT_ACK;
				break;
			default:
				DPRINT("[cc] unrecognised msg");
				return CTRL_PARSE_ERROR;
		}
	}
	else if (strncmp("PASV", msg_str, 4) == 0) {
		msg->ctrl = PASV;
	}
	else if (strncmp("PORT", msg_str, 4) == 0) {
		int port_h, port_l;

		if (sscanf(msg_str + 5, "%d, %d", &port_h, &port_l) != 2) {
			return CTRL_PARSE_ERROR;
		}

		msg->ctrl = PORT;
		msg->port = (port_h << 8) | port_l;
	}
	else if (strncmp("RETR", msg_str, 4) == 0) {
		strncpy(msg->filename, msg_str + 5, FILENAME_MAX);
		msg->ctrl = RETR;
	}
	else if (strncmp("STOR", msg_str, 4) == 0) {
		strncpy(msg->filename, msg_str + 5, FILENAME_MAX);
		msg->ctrl = STOR;
	}
	else {
		DPRINT("[cc] unrecognised msg");
		return CTRL_PARSE_ERROR;
	}

	return CTRL_SUCCESS;
}

static int ctrl_to_message(const ctrl_message* msg, char* buffer)
{
	switch (msg->ctrl) {

	case PASV:
		snprintf(buffer, MAX_MSGLEN, "PASV\r\n");
		break;
	case PASV_ACK:
		snprintf(buffer, MAX_MSGLEN, "227 Passive %d, %d\r\n", (msg->port >> 8) & 0xFF, msg->port & 0xFF);
		break;
	case PORT:
		snprintf(buffer, MAX_MSGLEN, "PORT %d, %d\r\n", (msg->port >> 8) & 0xFF, msg->port & 0xFF);
		break;
	case PORT_ACK:
		snprintf(buffer, MAX_MSGLEN, "200 Command okay.\r\n");
		break;
	case RETR:
		snprintf(buffer, MAX_MSGLEN, "RETR %s\r\n", msg->filename);
		break;
	case STOR:
		snprintf(buffer, MAX_MSGLEN, "STOR %s\r\n", msg->filename);
		break;
	case ERR_NO_ACTION:
		snprintf(buffer, MAX_MSGLEN, "550 Requested action not taken.\r\n");
		break;
	case OPEN_DATA:
		snprintf(buffer, MAX_MSGLEN, "150 About to open a data connection.\r\n");
		break;
	case ERR_OPEN_DATA:
		snprintf(buffer, MAX_MSGLEN, "425 Can't open data connection.\r\n");
		break;
	case ERR_DATA_CLOSED:
		snprintf(buffer, MAX_MSGLEN, "426 Connection closed; transfer aborted.\r\n");
		break;
	case DATA_FIN:
		snprintf(buffer, MAX_MSGLEN, "226 Requested file action successful.\r\n");
		break;
	case SYNTAX_ERROR:
		snprintf(buffer, MAX_MSGLEN, "501 syntax error.\r\n");
		break;
	default:
		assert(0);
		return CTRL_ERROR;
	}

	return CTRL_SUCCESS;
}
